/*==========================================================================*\
 |  $Id: Variable.java 396 2011-04-10 20:37:18Z vtwoods@gmail.com $
 |*-------------------------------------------------------------------------*|
 |  Copyright (C) 2009 Virginia Tech
 |
 |  This file is part of CloudSpace.
 |
 |  CloudSpace is free software; you can redistribute it and/or modify
 |  it under the terms of the GNU General Public License as published
 |  by the Free Software Foundation; either version 3 of the License, or
 |  (at your option) any later version.
 |
 |  CloudSpace is distributed in the hope that it will be useful,
 |  but WITHOUT ANY WARRANTY; without even the implied warranty of
 |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 |  GNU General Public License for more details.
 |
 |  You should have received a copy of the GNU General Public License
 |  along with CloudSpace; if not, see <http://www.gnu.org/licenses/>.
\*==========================================================================*/

package student.web;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.zkoss.zk.ui.Executions;

import student.web.internal.Exceptions;
import student.web.internal.JavaStatementElement;

// -------------------------------------------------------------------------
/**
 * A ZUL variable declaration.
 * <p>
 * A &lt;variable&gt; tag has two required properties and one optional property:
 * </p>
 * <dl>
 * <dt><b>Parameters</b></dt>
 * <dd><code>type</code> - The (optional) Java type of the variable--this should
 * be a legal Java class name that is in scope (see {@link Import}) (<b>must</b>
 * come before the value!).</dd>
 * <dd><code>name</code> - The variable name (<b>must</b> come before the
 * value!)</dd>
 * <dd><code>value</code> - The variable's value. It can be any Java expression,
 * or any EL expression (i.e., ${...}), but may not be a bound data value (no
 *
 * @{... ).</dd>
 *       </dl>
 *       <p>
 *       Examples:
 *       </p>
 *
 *       <pre>
 * &lt;variable name="x" value="new Person()"/&gt;
 *
 * &lt;!-- With an included type --&gt;
 * &lt;variable type="Person" name="x" value="new Person()"/&gt;
 *
 * &lt;!-- If you use quotes, they must be HTML-escaped --&gt;
 * &lt;variable name="file"
 *   value="student.IOHelper.getFile(&amp;quot;infile.txt&amp;quot;)"/&gt;
 *
 * &lt;!-- A longer version, so you don't have to escape the quotes. --&gt;
 * &lt;variable name="file2"&gt;
 *   &lt;attribute name="value"&gt;
 *     student.IOHelper.getFile("infile.txt")
 *   &lt;/attribute&gt;
 * &lt;/variable&gt;
 * </pre>
 *
 * @author Stephen Edwards
 * @author Last changed by $Author: vtwoods@gmail.com $
 * @version $Revision: 396 $, $Date:: 2011-04-10#$
 */
public class Variable extends JavaStatementElement
{
    //~ Instance/static variables .............................................

    private String type;
    private String name;
    private String valueExpr;
    private boolean isDefined = false;

    private static final long serialVersionUID = -693164625468692787L;


    //~ Constructor ...........................................................

    // ----------------------------------------------------------
    /**
     * Creates a new Variable component.
     */
    public Variable()
    {
        super();
        setVisible(false);
    }


    //~ Public Methods ........................................................

    // ----------------------------------------------------------
    /**
     * Accessor for the "name" property.
     *
     * @return The variable's name
     */
    public String getName()
    {
        return name;
    }


    // ----------------------------------------------------------
    /**
     * Mutator for the "name" property. Note that the variable name must be
     * set before the value (which means the name attribute must appear to
     * the left of the value attribute in your ZHTML code).
     *
     * @param newName The name to use for the variable.
     */
    public void setName(String newName)
    {
        name = newName;
        tryToDefine();
    }


    // ----------------------------------------------------------
    /**
     * Accessor for the "type" property, which may be null if no type has
     * been specified.
     *
     * @return The variable's type (a Java {@link Class} name).
     */
    public String getType()
    {
        return type;
    }


    // ----------------------------------------------------------
    /**
     * Mutator for the "type" property, which is optional. Note that the
     * variable type, if used, must be set before the value (which means
     * the type attribute must appear to the left of the value attribute
     * in your ZHTML code).
     *
     * @param newType The type (class) to use for the variable. The class
     *                name specified must either be fully qualified, or must
     *                have been imported (see {@link Import}).
     */
    public void setType(String newType)
    {
        type = newType;
        tryToDefine();
    }


    // ----------------------------------------------------------
    /**
     * Mutator for the "value" property, which provides the variable's
     * initial value. It must come after the name and (optional) type.
     *
     * @param newValue The value to use for the variable. It must either be
     *                 an object of the appropriate type, or a string that
     *                 can be interpreted as a Java expression of the
     *                 appropriate type.
     */
    public void setValue(String newValue)
    {
        valueExpr = newValue;
        tryToDefine();
    }


    //~ Private Methods .......................................................

    // ----------------------------------------------------------
    private void tryToDefine()
    {
        if (!isDefined && name != null)
        {
            if (type == null)
            {
                type = "Object";
            }
            if (type.equals("Object"))
            {
                type = "java.lang.Object";
            }
            getPage().getInterpreter(getPage().getZScriptLanguage())
                .interpret(type + " " + getName() + ";", getParent());
            isDefined = true;
        }
        if (isDefined && valueExpr != null)
        {
            if ( valueExpr.indexOf( "${" ) >= 0 )
            {
                getParent().setAttribute(
                    name,
                    Executions.evaluate(this, valueExpr, Object.class),
                    false);
            }
            else
            {
                if ( valueExpr.indexOf( "@{" ) >= 0 )
                {
                    valueExpr = valueExpr.trim();
                    valueExpr = valueExpr.substring(2);
                    valueExpr = valueExpr.substring(0, valueExpr.length() - 1);
                }
                try
                {
                    getPage().getInterpreter(getPage().getZScriptLanguage())
                        .interpret(getName() + " = " + valueExpr + ";",
                            getParent());
                }
                catch (RuntimeException e)
                {
                    Throwable cause = Exceptions.getRealCause(e);
                    if (cause instanceof bsh.EvalError)
                    {
                        bsh.EvalError error = (bsh.EvalError)cause;
                        String message = error.getMessage();
                        if (message != null)
                        {
                            Matcher m = BAD_VALUE_EXPRESSION.matcher(message);
                            if (m.find())
                            {
                                message = "Unable to evaluate value "
                                    + m.group(1)
                                    + " for <variable> tag with name \""
                                    + name
                                    + "\"\n"
                                    + message.substring(m.end());
                            }
                        }
                        error.setMessage(message);
                    }

                    // Re-throw the possibly modified exception
                    throw e;
                }
            }
        }
    }

    private static final Pattern BAD_VALUE_EXPRESSION =
        Pattern.compile("Sourced\\s+file:\\s+inline\\s+[^=]*=\\s+"
            + "([^;]+);\\s*\n", Pattern.DOTALL);
}